package com.liuzhiqiang.oauth.confing.oauth;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.liuzhiqiang.oauth.vo.AjaxResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;

import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

/**
 * @author 刘志强
 * @title: OAuth2Config
 * @projectName housekeeper-data-center-back
 * @description: TODO
 * @date 2021/7/2919:50
 */
@Configuration
// 启用授权服务
@EnableAuthorizationServer
@Slf4j
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    /**
     * token 对应权限信息的存储
     */
    @Autowired
    private TokenStore tokenStore;
    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    /**
     * 密码的校验方式
     */
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private HttpServletResponse httpServletResponse;

    /**
     * 创建一个 token商店，
     * InMemoryTokenStore 基于内存，token对应的权限信息会存储到inMemoryTokenStore对象中
     *
     * @return
     */
    @Bean(name = "tokenStore")
    public TokenStore tokenStore() {
        InMemoryTokenStore inMemoryTokenStore = new InMemoryTokenStore();
        return inMemoryTokenStore;
    }

//    @Bean(name = "tokenStore")
//    public TokenStore tokenStore(RedisConnectionFactory redisConnectionFactory) {
//        RedisTokenStore redis = new RedisTokenStore(redisConnectionFactory);
//        return redis;
//    }

    /**
     * 授权服务配置
     * tokenStore 登录后 最终将权限信息存入tokenStore。
     * authenticationManager 配置 身份验证管理器。登陆时查询用户权限信息，密码加密方式验证
     * exceptionTranslator 登录异常时的处理器
     *
     * @param endpoints
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.tokenStore(tokenStore)
                // 自定义 身份验证管理器
                .authenticationManager(authentication -> {
                    log.info("authentication:{}", authentication);
                    return authenticationProvider().authenticate(authentication);
                })
                .exceptionTranslator(e -> {
                    httpServletResponse.setContentType("application/json;charset=utf-8");
                    PrintWriter out;
                    out = httpServletResponse.getWriter();
                    out.write(new ObjectMapper().writeValueAsString(AjaxResult.error(HttpStatus.UNAUTHORIZED.value(), e.getMessage())));
                    out.flush();
                    out.close();
                    return null;
                })
                // 这里还要配置userDetailsService 是因为刷新token时还需要
                .userDetailsService(userDetailsService);
    }


    /**
     * 配置客户端信息
     * 当用户访问/oauth/token 接口时，需要的Authorization 信息，grant_type方式等
     * 如: /oauth/token?username=admin&password=1234562&grant_type=password&scope=ALL
     * Authorization Basic bGl1emhpcWlhbmc6MTIz
     * bGl1emhpcWlhbmc6MTIz 是 liuzhiqiang 和 123 Basic编码
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("liuzhiqiang")
                .secret(passwordEncoder.encode("123"))
                .scopes("ALL")
                .authorizedGrantTypes("refresh_token", "password")
                .accessTokenValiditySeconds(36000)
                .and()
                .withClient("wangYan")
                .secret(passwordEncoder.encode("123"))
                .scopes("USER")
                .authorizedGrantTypes("refresh_token", "password")
                .accessTokenValiditySeconds(36000);
    }

    /**
     * 配置权限服务的安全配置
     * /oauth/check_token 需要认证访问
     *
     * @param authorizationServer
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer authorizationServer) {
        authorizationServer
                .checkTokenAccess("permitAll()");
    }

    /**
     * 密码编码器
     *
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(10);
    }

    /**
     * 自定义AuthenticationProvider
     *
     * @return
     */
    @Bean(name = "authenticationProvider")
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
        return daoAuthenticationProvider;
    }

}
